/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */
#ifndef PLVECTOR_H
#define PLVECTOR_H

#include <kernel/PLObject.h>
#include <tools/PLIIterator.h>
#include <tools/PLIterator.h>
#include <tools/PLArray.h>
#include <exception/PLIndexOutOfRangeException.h>
#include <exception/PLElementNotFoundException.h>

namespace PLSDK {

	/**
	 * Vector for objects.
	 */
	template<typename T>
	class PLVector : public virtual PLObject, public PLIIterator<T> {
	public:
		/**
		 * Constructor.
		 *
         * @param clear Clear the allocated memory.
		 */
		PLVector(bool clear = false);
		/**
		 * Constructor.
		 *
		 * @param size The initial size of the vector.
		 * @param expandSize The expand size for the expand if there was not space.
         * @param clear Clear the allocated memory.
		 */
		PLVector(pluint size, pluint expandSize = 10, bool clear = false);
		/**
		 * Copy Constructor.
		 *
		 * @param vector The vector to copy.
		 */
		PLVector(const PLVector<T> &vector);
		/**
		 * The set operator for the vector.
		 */
		PLVector<T>& operator=( const PLVector<T>& );
		/**
		 * Add a new object to the vector at position index.
		 *
		 * @param object The object to add to the vector.
		 * @return The index position where the object was added.
		 */
		inline pluint add(T object);
		/**
		 * Add a new object to the vector at position index.
		 *
		 * @param object The object to add to the vector.
		 * @param index The index you want to add the object.
		 * @return The index position where the object was added.
		 */
		inline pluint add(T object, pluint index);
		/**
		 * Is the vector empty.
		 *
		 * @return True if the vector was empty.
		 */
		inline plbool isEmpty();
		/**
		 * Set the minimum buffer size for the vector. No data was deleted.
		 *
		 * @param size The new minimum size of the vector.
		 */
		inline void expandToMin(pluint size);
		/**
		 * Get the capacity of the vector.
		 *
		 * @return The capacity of the vector.
		 */
		inline pluint capacity();
		/**
		 * Remove all objects out of the vector.
		 */
		inline void clear();
		/**
		 * Contains the vector a object.
		 *
		 * @param object The object to test for.
		 * @return True if the object was in the vector.
		 */
		inline plbool contains(T object);
		/**
		 * Get the number of objects in the vector.
		 *
		 * @return The number of objects.
		 */
		inline const pluint &count() const;
		/**
		 * Get the object from the vector at position index.
		 *
		 * @param index The index from the object.
		 * @return The object at position index.
		 * @return PLIndexOutOfRangeException The element at the position index not exists.
		 */
		inline T get(pluint index) const;
		/**
		 * Get the object from the vector at position index.
		 *
		 * @param index The index from the object.
		 * @return The object at position index.
		 * @exception PLIndexOutOfRangeException The element at the position index not exists.
		 */
		inline T at(pluint index) const;
		/**
		 * Get the index of a object in the vector.
		 *
		 * @param object The object to get the index for.
		 * @return The index of the object.
		 * @exception PLElementNotFoundException The element to search for was not found.
		 */
		inline pluint indexOf(T object);
		/**
		 * Get the last index of a object in the vector.
		 *
		 * @param object The object to get the last index for.
		 * @return The last index of the object.
		 * @exception PLElementNotFoundException The element to search for was not found.
		 */
		inline pluint lastIndexOf(T object);
		/**
		 * Remove a object at position index.
		 *
		 * @param index The index of the object to remove.
		 * @exception PLElementNotFoundException The elementen to delete was not found.
		 */
		inline void removeElement(pluint index);
		/**
		 * Remove one object from the vector. Only the first match was removed.
		 *
		 * @param object The first object to remove from the vector.
		 * @exception PLElementNotFoundException The element to delete was not found.
		 */
		inline void remove(T object);
		/**
		 * Remove all object from the vector. It is not so fast because we must
		 * change the position of all other elements after we have one element
		 * removed.
		 *
		 * @param object The object to remove from the vector.
		 * @exception PLElementNotFoundException No element to delete was not found.
		 */
		inline void removeAll(T object);
		/**
		 * Set a object to a index in the vector. If no object exists on the index the
		 * object was added to the vector.
		 *
		 * @param index The index of the object to set.
		 * @param object The object to set.
		 * @param addnew True to add the element if it not exists. It this was set to false the
		 * PLElementNotFoundException exception was thrown it the element not exists.
		 * @return The object that was before on the position index.
		 */
		inline T set(pluint index, T object, plbool addnew = true);
		/**
		 * Return the data of the vector as array.
		 *
		 * @return The vector data of the vector.
		 * @warning The return array was not deleted from this class it was in you're hand to
		 * delete it.
		 */
		inline T *toArray();
		/**
		 * Have the current position have a next object.
		 *
		 * @param pos The current position.
		 * @return true if the current position have a next object.
		 */
		inline plbool hasNext(pluint pos);
		/**
		 * Have the current position have a previous object.
		 *
		 * @param pos The current position.
		 * @return true if the current position have a previous object.
		 */
		inline plbool hasPrevious(pluint pos);
		/**
		 * The next object of the vector.
		 *
		 * @param pos The current position.
		 * @return The next object.
		 */
		inline T next(pluint pos);
		/**
		 * The previous object of the vector.
		 *
		 * @param pos The current position.
		 * @return The previous object.
		 */
		inline T previous(pluint pos);
		/**
		 * Return the current object.
		 *
		 * @return The object.
		 */
		inline T current(pluint pos);
		/**
		 * Get the first element from the vector.
		 *
		 * @return The first element from the vector.
		 * @exception PLElementNotFoundException The element to search for was not found.
		 */
		inline T first();
		/**
		 * Get the last element from the vector.
		 *
		 * @return The last element from the vector.
		 */
		inline T last();
		/**
		 * Get a iterator for the vector.
		 *
		 * @return A iterator for the vector.
		 */
		inline PLIterator<T> iterator();
		/**
		 * Get the hash value of the object.
		 *
		 * @return The hash value of the object. The hash for the PLObject object was 1.
		 * @todo implement a better hash value.
		 */
		inline virtual pluint hash() const;
		/**
		 * Compare to objects.
		 *
		 * @param object Object to compare with.
		 * @return The result of the compare.
		 * @todo implement
		 */
		inline virtual PLCompareResult compareTo(PLVector<T> &object);
		/**
		 * Compare to objects.
		 *
		 * @param object Object to compare with.
		 * @return The result of the compare.
		 * @todo implement
		 */
		inline virtual PLCompareResult compareTo(PLObject *object);
		/**
		 * Operator to access the vector like a array.
		 *
		 * @exception PLIndexOutOfRangeException The element at the position index not exists.
		 */
		inline T &operator[](pluint pos);
		/**
		 * Operator to access the vector like a array.
		 *
		 * @exception PLIndexOutOfRangeException The element at the position index not exists.
		 */
		inline const T &operator[](pluint pos) const;
		/**
		 * Get the pointer of the data from the vector.
		 *
		 * @return The pointer to the data of the vector.
		 * @warning The return array was not deleted from this class it was in you're hand to
		 * delete it.
		 */
		inline const T *constData() const;
		/**
		 * Get the pointer of the data from the vector.
		 *
		 * @return The pointer to the data of the vector.
		 * @warning The return array was not deleted from this class it was in you're hand to
		 * delete it.
		 */
		inline T *data() const;
	private:
		void enlarge();
		PLArray<T> m_items;
		pluint m_expSize;
		pluint m_itemCount;
        bool m_clear;
	};

    template<typename T>
    PLVector<T>::PLVector(bool _clear) : PLObject(), m_items(_clear), m_itemCount(0), m_clear(_clear) {
        m_expSize = 10;
		m_items.resetSizeOfArray(10);
    }

	template<typename T>
	PLVector<T>::PLVector(pluint size, pluint expandSize, bool _clear) : PLObject(), m_items(_clear), m_itemCount(0), m_clear(_clear) {
		if(expandSize > 0) {
			m_expSize = expandSize;
		} else {
			m_expSize = 10;
		}
		m_items.resetSizeOfArray(size);
	}

	template<typename T>
	PLVector<T>::PLVector(const PLVector<T> &vector) : PLObject(), m_items(), m_expSize(vector.m_expSize), m_itemCount(vector.m_itemCount), m_clear(vector.m_clear) {
		m_items = vector.m_items;
	}

	template<typename T>
	PLVector<T>& PLVector<T>::operator=( const PLVector<T> &vector ) {
		m_expSize	= vector.m_expSize;
		m_itemCount = vector.m_itemCount;
		m_items		= vector.m_items;
		return *this;
	}

	template<typename T>
	pluint PLVector<T>::add(T object) {
		return add(object, m_itemCount);
	}

	template<typename T>
	pluint PLVector<T>::add(T object, pluint index) {
		if(m_itemCount + 1 >= m_items.size()) {
			enlarge();
		}
		if(index > m_itemCount) {
			m_items[m_itemCount] = object;
			m_itemCount++;
			return m_itemCount - 1;
		}
		T _cur = m_items[index];
		T _next;
		m_items[index] = object;
		for(pluint j = index + 1; j < m_itemCount + 1; j++) {
			_next       = m_items[j];
			m_items[j]  = _cur;
			_cur        = _next;
		}
		m_itemCount++;
		return index;
	}

	template<typename T>
	void PLVector<T>::expandToMin(pluint size) {
		if(m_itemCount < size) {
			m_items.resetSizeOfArray(size, true);
		}
	}

	template<typename T>
	plbool PLVector<T>::isEmpty() {
		return m_items.isEmpty();
	}

	template<typename T>
	pluint PLVector<T>::capacity() {
		return m_items.size();
	}

	template<typename T>
	void PLVector<T>::clear() {
		m_itemCount = 0;
	}

	template<typename T>
	plbool PLVector<T>::contains(T object) {
		for(pluint i = 0 ; i < m_items.size() ; i++) {
			if(m_items[i] == object) {
				return true;
			}
		}
		return false;
	}

	template<typename T>
	const pluint &PLVector<T>::count() const {
		return m_itemCount;
	}

	template<typename T>
	T PLVector<T>::at(pluint index) const {
		return get(index);
	}

	template<typename T>
	void PLVector<T>::enlarge() {
		m_items.resetSizeOfArray(m_items.size() + m_expSize, true);
	}

	template<typename T>
	T PLVector<T>::get(pluint index) const {
		if(index <= m_itemCount) {
			return m_items.get(index);
		}
		plthrow(PLIndexOutOfRangeException);
        static T ret;
        return ret;
	}

	template<typename T>
	pluint PLVector<T>::indexOf(T object) {
		for(pluint i = 0 ; i < m_itemCount ; i++) {
			if(m_items[i] == object) {
				return i;
			}
		}
		plthrow(PLElementNotFoundException);
	}

	template<typename T>
	pluint PLVector<T>::lastIndexOf(T object) {
		for(pluint i = m_itemCount - 1 ; i > -1 ; i--) {
			if(m_items[i] == object) {
				return i;
			}
		}
		plthrow(PLElementNotFoundException);
	}

	template<typename T>
	void PLVector<T>::removeElement(pluint index) {
		if(index < m_itemCount) {
			for(pluint i = index ; i < m_itemCount ; i++) {
				m_items[i] = m_items[i + 1];
			}
			m_itemCount--;
			return;
		}
		plthrow(PLElementNotFoundException);
	}

	template<typename T>
	void PLVector<T>::remove(T object) {
		pluint i;
		for(pluint j = 0 ; j < m_itemCount ; j++) {
			if(m_items[j] == object) {
				for(i = j ; i < m_itemCount ; i++) {
					m_items[i] = m_items[i + 1];
				}
				m_itemCount--;
				return;
			}
		}
		plthrow(PLElementNotFoundException);
	}

	template<typename T>
	void PLVector<T>::removeAll(T object) {
		plbool ret = false;
		pluint i;
		for(pluint j = 0 ; j < m_itemCount ; j++) {
			if(m_items[j] == object) {
				for(i = j ; i < m_itemCount ; i++) {
					m_items[i] = m_items[i + 1];
				}
				m_itemCount--;
				ret = true;
			}
		}
		if(!ret) {
			plthrow(PLElementNotFoundException);
		}
	}

	template<typename T>
	T PLVector<T>::set(pluint index, T object, plbool addnew) {
		T _temp;
		if(index < m_itemCount) {
			_temp = m_items[index];
			m_items[index] = object;
		} else if(addnew) {
			add(object, index);
		} else {
			plthrow(PLElementNotFoundException);
		}
		return _temp;
	}

	template<typename T>
	T *PLVector<T>::toArray() {
		T *obj = new T[m_itemCount];
		for(pluint i = 0 ; i < m_itemCount ; i++) {
			obj[i] = m_items[i];
		}
		return obj;
	}

	template<typename T>
	plbool PLVector<T>::hasNext(pluint pos) {
		if(pos < m_itemCount) {
			return true;
		}
		return false;
	}

	template<typename T>
	plbool PLVector<T>::hasPrevious(pluint pos) {
        PLUNUSED(pos)
        return true;
	}

	template<typename T>
	T PLVector<T>::next(pluint pos) {
		return m_items[pos];
	}

	template<typename T>
	T PLVector<T>::previous(pluint pos) {
		return m_items[pos];
	}

	template<typename T>
	T PLVector<T>::current(pluint pos) {
		return m_items[pos];
	}

	template<typename T>
	T PLVector<T>::first() {
		if(m_itemCount > 0) {
			return m_items[0];
		}
		plthrow(PLElementNotFoundException);
	}

	template<typename T>
	T PLVector<T>::last() {
		if(m_itemCount > 0) {
			return m_items[m_itemCount - 1];
		}
		plthrow(PLElementNotFoundException);
	}

	template<typename T>
	PLIterator<T> PLVector<T>::iterator() {
		return PLIterator<T>(this);
	}

	template<typename T>
	pluint PLVector<T>::hash() const {
		return 2;
	}

	template<typename T>
	PLCompareResult PLVector<T>::compareTo(PLVector<T> &/*object*/) {
		return PLCompareResultSmaller;
	}

	template<typename T>
	PLCompareResult PLVector<T>::compareTo(PLObject */*object*/) {
		return PLCompareResultSmaller;
	}

	template<typename T>
	T &PLVector<T>::operator[](pluint pos) {
		if(pos < capacity()) {
			return m_items[pos];
		}
		plthrow(PLIndexOutOfRangeException);
        static T ret;
        return ret;
	}
	
	template<typename T>
	const T &PLVector<T>::operator[](pluint pos) const {
		if(pos < capacity()) {
			return m_items[pos];
		}
		plthrow(PLIndexOutOfRangeException);
	}
	
	template<typename T>
	const T *PLVector<T>::constData() const {
		return m_items.constData();
	}

	template<typename T>
	T *PLVector<T>::data() const {
		return m_items.data();
	}

}

#endif
